Skip to content

Add health_check MCP tool diagnostic handler#73

Merged
jeffgreendesign merged 2 commits intomainfrom
codex/add-health_check-tool-to-textrawl-server
Apr 4, 2026
Merged

Add health_check MCP tool diagnostic handler#73
jeffgreendesign merged 2 commits intomainfrom
codex/add-health_check-tool-to-textrawl-server

Conversation

@jeffgreendesign
Copy link
Copy Markdown
Owner

Summary

  • implemented a health_check MCP tool with the requested schema and no input parameters (inputSchema: {})
  • added handleHealthCheck() with independent try/catch checks for:
    • database connectivity (SELECT 1 with latency)
    • documents
    • chunks
    • memory entities (memory_entities)
    • conversations (conversation_sessions)
    • insights (proactive_insights)
  • returns overall status (healthy / degraded / unhealthy), per-check details, and timestamp
  • wired response through existing toolResponse(...) pattern and errors through toolError(...)
  • added health_check to the static tool registry list in src/api/status.ts
  • updated health tool unit tests to match the new no-arg schema and handler behavior

Verification

  • pnpm vitest run src/tools/__tests__/health.test.ts
  • pnpm build
  • pre-commit hook also ran pnpm verify:fast successfully (quality, tests, security/docs/tool-sync checks)

Notes

  • kept this change scoped only to health_check registration/handler behavior and its related status listing/tests.

Codex Task

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Building Building Preview, Comment Apr 4, 2026 7:04pm
textrawl Ready Ready Preview, Comment Apr 4, 2026 7:04pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 4, 2026

Walkthrough

The PR adds a static health_check entry to tool definitions so /status and the dashboard list it. The health tool was simplified: it no longer uses embeddings or model checks, takes no input, and returns a timestamp. It requires DATABASE_URL and runs fixed COUNT(*) queries against documents, chunks, memory_entities, conversation_sessions, and proactive_insights, collecting per-component checks and computing overall status as healthy (all OK), degraded (some OK), or unhealthy (none OK).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant StatusAPI as Status API
  participant HealthTool as Health Tool
  participant Postgres as Postgres DB
  participant Dashboard

  Client->>StatusAPI: GET /status
  StatusAPI->>HealthTool: invoke health_check handler
  HealthTool->>HealthTool: verify DATABASE_URL configured
  alt DB not configured
    HealthTool-->>StatusAPI: configError (Database)
  else DB configured
    HealthTool->>Postgres: SELECT COUNT(*) FROM documents
    Postgres-->>HealthTool: count
    HealthTool->>Postgres: SELECT COUNT(*) FROM chunks
    Postgres-->>HealthTool: count
    HealthTool->>Postgres: SELECT COUNT(*) FROM memory_entities
    Postgres-->>HealthTool: count
    HealthTool->>Postgres: SELECT COUNT(*) FROM conversation_sessions
    Postgres-->>HealthTool: count / error
    HealthTool->>Postgres: SELECT COUNT(*) FROM proactive_insights
    Postgres-->>HealthTool: count
    HealthTool->>HealthTool: aggregate component checks -> status (healthy/degraded/unhealthy)
    HealthTool-->>StatusAPI: structured health result (timestamp, checks, status)
  end
  StatusAPI-->>Client: /status response (includes health_check)
  StatusAPI->>Dashboard: include tool definition / reported status
  Dashboard-->>Client: UI shows MCP Tools including health_check
Loading
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing and adding a health_check MCP tool diagnostic handler to the codebase.
Description check ✅ Passed The description comprehensively covers the summary, changes, verification steps, and notes, though it lacks explicit checkboxes completion for the template's checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-health_check-tool-to-textrawl-server

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/tools/__tests__/health.test.ts (1)

88-92: Consider adding an empty-DB test case.

Per project learnings, new tools should include an output schema smoke test covering the empty-DB case. While the current schema validation test passes, adding an explicit test where countRows returns 0 for all tables would strengthen coverage.

📝 Suggested additional test
it('handles empty tables gracefully', async () => {
	vi.mocked(pgQuery).mockImplementation(async (sql: string) => {
		if (sql === 'SELECT 1') return { rows: [{ '?column?': 1 }], rowCount: 1 };
		return { rows: [{ c: 0 }], rowCount: 1 };
	});
	const result = (await callHealth()) as {
		structuredContent?: { status: string; checks: Record<string, { count?: number }> };
	};
	expect(result.structuredContent?.status).toBe('healthy');
	expect(result.structuredContent?.checks.documents.count).toBe(0);
});

Based on learnings: "New tools must include an output schema smoke test covering the empty-DB case."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/__tests__/health.test.ts` around lines 88 - 92, Add an explicit
"empty-DB" unit test to src/tools/__tests__/health.test.ts that mocks pgQuery so
countRows returns 0 for tables and the health check still conforms to
HealthCheckOutputSchema; specifically, create a test (e.g., it('handles empty
tables gracefully')) that vi.mocked(pgQuery).mockImplementation to return the
SELECT 1 probe response and zero counts for other queries, call callHealth(),
assert result.structuredContent.status is 'healthy' and that
result.structuredContent.checks.documents.count === 0, and ensure the output
still validates against HealthCheckOutputSchema.
src/tools/health.ts (1)

22-25: Consider using a string literal union type for table safety.

While currently safe (all callers pass hardcoded literals), adding type-level constraints prevents accidental misuse during future refactoring.

🛡️ Optional defensive typing
+type HealthTable =
+	| 'documents'
+	| 'chunks'
+	| 'memory_entities'
+	| 'conversation_sessions'
+	| 'proactive_insights';
+
-async function countRows(table: string): Promise<number> {
+async function countRows(table: HealthTable): Promise<number> {
 	const result = await pgQuery<{ c: number }>(`SELECT COUNT(*)::int AS c FROM ${table}`);
 	return result.rows[0]?.c ?? 0;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/health.ts` around lines 22 - 25, Replace the untyped string
parameter on countRows(table: string) with a string literal union type listing
the allowed table names (e.g., type TableName = '...') and change the signature
to countRows(table: TableName): Promise<number>; update any callers if they rely
on non-literal values and export the TableName type if other modules need it.
This constrains usages at the type level while keeping the function body
(pgQuery and SQL string) unchanged, preventing accidental future refactor bugs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/tools/__tests__/health.test.ts`:
- Around line 88-92: Add an explicit "empty-DB" unit test to
src/tools/__tests__/health.test.ts that mocks pgQuery so countRows returns 0 for
tables and the health check still conforms to HealthCheckOutputSchema;
specifically, create a test (e.g., it('handles empty tables gracefully')) that
vi.mocked(pgQuery).mockImplementation to return the SELECT 1 probe response and
zero counts for other queries, call callHealth(), assert
result.structuredContent.status is 'healthy' and that
result.structuredContent.checks.documents.count === 0, and ensure the output
still validates against HealthCheckOutputSchema.

In `@src/tools/health.ts`:
- Around line 22-25: Replace the untyped string parameter on countRows(table:
string) with a string literal union type listing the allowed table names (e.g.,
type TableName = '...') and change the signature to countRows(table: TableName):
Promise<number>; update any callers if they rely on non-literal values and
export the TableName type if other modules need it. This constrains usages at
the type level while keeping the function body (pgQuery and SQL string)
unchanged, preventing accidental future refactor bugs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23e34072-da2f-4231-b2a8-9f33a87c2afb

📥 Commits

Reviewing files that changed from the base of the PR and between 04db08c and edba1ea.

📒 Files selected for processing (3)
  • src/api/status.ts
  • src/tools/__tests__/health.test.ts
  • src/tools/health.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/tools/__tests__/health.test.ts (1)

39-113: Consider adding a test for the unhealthy status.

The test suite covers healthy and degraded statuses but not the unhealthy case where all checks fail. Since this is a diagnostic tool that will be "the first call when other tools are failing," verifying the all-failed scenario adds confidence.

💡 Example test for unhealthy status
it('returns unhealthy when all checks fail', async () => {
	vi.mocked(pgQuery).mockRejectedValue(new Error('connection refused'));
	const result = (await callHealth()) as {
		structuredContent?: { status: string };
	};
	expect(result.structuredContent?.status).toBe('unhealthy');
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/__tests__/health.test.ts` around lines 39 - 113, Add a test that
verifies the "unhealthy" status by forcing all subsystem checks to fail (mock
pgQuery used by createHandler/callHealth to reject or throw), call
createHandler() and assert that the returned structuredContent.status equals
'unhealthy' (and optionally validate the structure with
HealthCheckOutputSchema/z.object). Specifically, add a new it('returns unhealthy
when all checks fail', ...) that uses vi.mocked(pgQuery).mockRejectedValue(new
Error('connection refused')) (or similar), invokes callHealth(), and expects
result.structuredContent?.status toBe('unhealthy').
src/tools/health.ts (1)

29-32: SQL injection risk via direct table name interpolation.

While TableName is a string literal union type that restricts valid values at compile time, the table parameter is interpolated directly into the SQL string. If this function were ever called with a value that bypasses TypeScript checking (e.g., from a type assertion or any), it could enable SQL injection.

Consider using a safelist approach for defense-in-depth:

🛡️ Proposed fix with runtime validation
+const ALLOWED_TABLES: ReadonlySet<TableName> = new Set([
+	'documents',
+	'chunks',
+	'memory_entities',
+	'conversation_sessions',
+	'proactive_insights',
+]);
+
 async function countRows(table: TableName): Promise<number> {
+	if (!ALLOWED_TABLES.has(table)) {
+		throw new Error(`Invalid table name: ${table}`);
+	}
 	const result = await pgQuery<{ c: number }>(`SELECT COUNT(*)::int AS c FROM ${table}`);
 	return result.rows[0]?.c ?? 0;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/health.ts` around lines 29 - 32, The countRows function currently
interpolates the table identifier directly into SQL, creating a potential SQL
injection vector if TypeScript checks are bypassed; change countRows to perform
runtime validation against a safelist of allowed table names (e.g., a const Map
or object keyed by TableName -> actual table identifier) and use the mapped,
validated identifier in the query, throwing an error for unknown values;
reference the countRows function, the TableName type, and pgQuery when
implementing this runtime whitelist check to ensure only approved identifiers
are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/tools/__tests__/health.test.ts`:
- Around line 39-113: Add a test that verifies the "unhealthy" status by forcing
all subsystem checks to fail (mock pgQuery used by createHandler/callHealth to
reject or throw), call createHandler() and assert that the returned
structuredContent.status equals 'unhealthy' (and optionally validate the
structure with HealthCheckOutputSchema/z.object). Specifically, add a new
it('returns unhealthy when all checks fail', ...) that uses
vi.mocked(pgQuery).mockRejectedValue(new Error('connection refused')) (or
similar), invokes callHealth(), and expects result.structuredContent?.status
toBe('unhealthy').

In `@src/tools/health.ts`:
- Around line 29-32: The countRows function currently interpolates the table
identifier directly into SQL, creating a potential SQL injection vector if
TypeScript checks are bypassed; change countRows to perform runtime validation
against a safelist of allowed table names (e.g., a const Map or object keyed by
TableName -> actual table identifier) and use the mapped, validated identifier
in the query, throwing an error for unknown values; reference the countRows
function, the TableName type, and pgQuery when implementing this runtime
whitelist check to ensure only approved identifiers are used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 306979c7-5cf6-49e6-9308-44aa3753c0d0

📥 Commits

Reviewing files that changed from the base of the PR and between edba1ea and e33d5bd.

📒 Files selected for processing (2)
  • src/tools/__tests__/health.test.ts
  • src/tools/health.ts

@jeffgreendesign jeffgreendesign merged commit 6a766a6 into main Apr 4, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant